package android.bus.util;

import android.annotation.TargetApi;
import android.app.Activity;
import android.app.Application;
import android.bus.EventBus;
import android.os.Build;
import android.os.Bundle;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentManager;
import android.util.Log;

/**
 * Central class for app that want to use event based error dialogs.<br/>
 * <br/>
 * How to use:
 * <ol>
 * <li>Set the {@link #factory} to configure dialogs for your app, typically in {@link Application#onCreate()}</li>
 * <li>Use one of {@link #attachTo(Activity)}, {@link #attachTo(Activity, boolean)} or
 * {@link #attachTo(Activity, boolean, Bundle)} in your Activity, typically in onCreate.</li>
 * </ol>
 * 
 * For more complex mappings, you can supply your own {@link ErrorDialogFragmentFactory}.
 * 
 * @author Markus
 */
public class ErrorDialogManager {

	public static class SupportManagerFragment extends Fragment {
		protected boolean finishAfterDialog;
		protected Bundle argumentsForErrorDialog;
		private EventBus eventBus;
		private boolean skipRegisterOnNextResume;
		private Object executionScope;

		@Override
		public void onCreate(Bundle savedInstanceState) {
			super.onCreate(savedInstanceState);
			eventBus = ErrorDialogManager.factory.config.getEventBus();
			eventBus.register(this);
			skipRegisterOnNextResume = true;
		}

		@Override
		public void onResume() {
			super.onResume();
			if (skipRegisterOnNextResume) {
				// registered in onCreate, skip registration in this run
				skipRegisterOnNextResume = false;
			} else {
				eventBus = ErrorDialogManager.factory.config.getEventBus();
				eventBus.register(this);
			}
		}

		@Override
		public void onPause() {
			eventBus.unregister(this);
			super.onPause();
		}

		public void onEventMainThread(ThrowableFailureEvent event) {
			if (!isInExecutionScope(executionScope, event)) {
				return;
			}
			checkLogException(event);
			// Execute pending commits before finding to avoid multiple error fragments being shown
			FragmentManager fm = getFragmentManager();
			fm.executePendingTransactions();

			DialogFragment existingFragment = (DialogFragment) fm.findFragmentByTag(TAG_ERROR_DIALOG);
			if (existingFragment != null) {
				// Just show the latest error
				existingFragment.dismiss();
			}

			DialogFragment errorFragment = (DialogFragment) factory
					.prepareErrorFragment(event, finishAfterDialog, argumentsForErrorDialog);
			if (errorFragment != null) {
				errorFragment.show(fm, TAG_ERROR_DIALOG);
			}
		}

		public static void attachTo(Activity activity, Object executionScope, boolean finishAfterDialog,
				Bundle argumentsForErrorDialog) {
			FragmentManager fm = ((FragmentActivity) activity).getSupportFragmentManager();
			SupportManagerFragment fragment = (SupportManagerFragment) fm.findFragmentByTag(TAG_ERROR_DIALOG_MANAGER);
			if (fragment == null) {
				fragment = new SupportManagerFragment();
				fm.beginTransaction().add(fragment, TAG_ERROR_DIALOG_MANAGER).commit();
				fm.executePendingTransactions();
			}
			fragment.finishAfterDialog = finishAfterDialog;
			fragment.argumentsForErrorDialog = argumentsForErrorDialog;
			fragment.executionScope = executionScope;
		}
	}

	@TargetApi(Build.VERSION_CODES.HONEYCOMB)
	public static class HoneycombManagerFragment extends android.app.Fragment {
		protected boolean finishAfterDialog;
		protected Bundle argumentsForErrorDialog;
		private EventBus eventBus;
		private Object executionScope;

		@Override
		public void onResume() {
			super.onResume();
			eventBus = ErrorDialogManager.factory.config.getEventBus();
			eventBus.register(this);
		}

		@Override
		public void onPause() {
			eventBus.unregister(this);
			super.onPause();
		}

		public void onEventMainThread(ThrowableFailureEvent event) {
			if (!isInExecutionScope(executionScope, event)) {
				return;
			}
			checkLogException(event);

			// Execute pending commits before finding to avoid multiple error fragments being shown
			android.app.FragmentManager fm = getFragmentManager();
			fm.executePendingTransactions();

			android.app.DialogFragment existingFragment = (android.app.DialogFragment) fm
					.findFragmentByTag(TAG_ERROR_DIALOG);
			if (existingFragment != null) {
				// Just show the latest error
				existingFragment.dismiss();
			}

			android.app.DialogFragment errorFragment = (android.app.DialogFragment) factory.prepareErrorFragment(event,
					finishAfterDialog, argumentsForErrorDialog);
			if (errorFragment != null) {
				errorFragment.show(fm, TAG_ERROR_DIALOG);
			}
		}

		public static void attachTo(Activity activity, Object executionScope, boolean finishAfterDialog,
				Bundle argumentsForErrorDialog) {
			android.app.FragmentManager fm = activity.getFragmentManager();
			HoneycombManagerFragment fragment = (HoneycombManagerFragment) fm
					.findFragmentByTag(TAG_ERROR_DIALOG_MANAGER);
			if (fragment == null) {
				fragment = new HoneycombManagerFragment();
				fm.beginTransaction().add(fragment, TAG_ERROR_DIALOG_MANAGER).commitAllowingStateLoss();
				fm.executePendingTransactions();
			}
			fragment.finishAfterDialog = finishAfterDialog;
			fragment.argumentsForErrorDialog = argumentsForErrorDialog;
			fragment.executionScope = executionScope;
		}
	}

	/** Must be set by the application. */
	public static ErrorDialogFragmentFactory<?> factory;

	protected static final String TAG_ERROR_DIALOG = "de.greenrobot.eventbus.error_dialog";
	protected static final String TAG_ERROR_DIALOG_MANAGER = "de.greenrobot.eventbus.error_dialog_manager";

	public static final String KEY_TITLE = "de.greenrobot.eventbus.errordialog.title";
	public static final String KEY_MESSAGE = "de.greenrobot.eventbus.errordialog.message";
	public static final String KEY_FINISH_AFTER_DIALOG = "de.greenrobot.eventbus.errordialog.finish_after_dialog";
	public static final String KEY_ICON_ID = "de.greenrobot.eventbus.errordialog.icon_id";
	public static final String KEY_EVENT_TYPE_ON_CLOSE = "de.greenrobot.eventbus.errordialog.event_type_on_close";

	/** Scope is limited to the activity's class. */
	public static void attachTo(Activity activity) {
		attachTo(activity, false, null);
	}

	/** Scope is limited to the activity's class. */
	public static void attachTo(Activity activity, boolean finishAfterDialog) {
		attachTo(activity, finishAfterDialog, null);
	}

	/** Scope is limited to the activity's class. */
	public static void attachTo(Activity activity, boolean finishAfterDialog, Bundle argumentsForErrorDialog) {
		Object executionScope = activity.getClass();
		attachTo(activity, executionScope, finishAfterDialog, argumentsForErrorDialog);
	}

	public static void attachTo(Activity activity, Object executionScope, boolean finishAfterDialog,
			Bundle argumentsForErrorDialog) {
		if (factory == null) {
			throw new RuntimeException("You must set the static factory field to configure error dialogs for your app.");
		}
		if (isSupportActivity(activity)) {
			SupportManagerFragment.attachTo(activity, executionScope, finishAfterDialog, argumentsForErrorDialog);
		} else {
			HoneycombManagerFragment.attachTo(activity, executionScope, finishAfterDialog, argumentsForErrorDialog);
		}
	}

	private static boolean isSupportActivity(Activity activity) {
		boolean isSupport = false;
		for (Class<?> c = activity.getClass().getSuperclass();; c = c.getSuperclass()) {
			if (c == null) {
				throw new RuntimeException("Illegal activity type: " + activity.getClass());
			}
			String name = c.getName();
			if (name.equals("android.support.v4.app.FragmentActivity")) {
				isSupport = true;
				break;
			} else if (name.startsWith("com.actionbarsherlock.app")
					&& (name.endsWith(".SherlockActivity") || name.endsWith(".SherlockListActivity") || name
							.endsWith(".SherlockPreferenceActivity"))) {
				throw new RuntimeException("Please use SherlockFragmentActivity. Illegal activity: " + name);
			} else if (name.equals("android.app.Activity")) {
				if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
					throw new RuntimeException(
							"Illegal activity without fragment support. Either use Android 3.0+ or android.support.v4.app.FragmentActivity.");
				}
				break;
			}
		}
		return isSupport;
	}

	protected static void checkLogException(ThrowableFailureEvent event) {
		if (factory.config.logExceptions) {
			String tag = factory.config.tagForLoggingExceptions;
			if (tag == null) {
				tag = EventBus.TAG;
			}
			Log.i(tag, "Error dialog manager received exception", event.throwable);
		}
	}

	private static boolean isInExecutionScope(Object executionScope, ThrowableFailureEvent event) {
		if (event != null) {
			Object eventExecutionScope = event.getExecutionScope();
			if (eventExecutionScope != null && !eventExecutionScope.equals(executionScope)) {
				// Event not in our scope, do nothing
				return false;
			}
		}
		return true;
	}

}
